﻿/*
Copyright (c) 2012 Maksim Gordeev

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE
 */

/*
 * Сделано в SharpDevelop.
 * Пользователь: maxxadm
 * Дата: 24.07.2012
 * Время: 17:00
 * 
 * Для изменения этого шаблона используйте Сервис | Настройка | Кодирование | Правка стандартных заголовков.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading;
using System.ComponentModel;
using Hammock;

namespace jellybird.core
{
	/// <summary>
	/// List of sorted tw objects, key property must be long type (as attribute "id" in TwTweet),
	/// Synchronized
	/// </summary>
	public abstract class TwObjectSortedList<T>:
		TwObjectBase,
	IList<T>,
	IList,
	INotifyPropertyChanged,
	INotifyCollectionChanged
		where T:TwObjectBase
	{
		public TwObjectSortedList(string KeyMapping,ListSortDirection SortOrder)
			:base()
		{
			init_list_intern(KeyMapping,SortOrder);
		}
		
		public TwObjectSortedList(RestClient client,string KeyMapping,ListSortDirection SortOrder)
			:base(client)
		{
			init_list_intern(KeyMapping,SortOrder);
		}
		
		private SortedList<long,T> list_intern;
		private PropertyDescriptor key_property;
//		private EventWaitHandle sync_handle;
		
		#region INotifyPropertyChanged implementation
		
		private bool m_NeedChangeNotificationInvoke;
		
		/// <summary>
		/// If setted, PropertyChanged and CollectionChanged
		/// will be invoked in application dispatcher thread
		/// </summary>
		public bool NeedChangeNotificationInvoke
		{
			get
			{
				bool ret=false;
				lock(SyncRoot)
				{
					ret=m_NeedChangeNotificationInvoke;
				}
				return ret;
			}
			set
			{
				lock(SyncRoot)
				{
					if(m_NeedChangeNotificationInvoke!=value)
						m_NeedChangeNotificationInvoke=value;
				}
			}
		}
		
		public event PropertyChangedEventHandler PropertyChanged;
		
		protected void On_property_changed(string property_name)
		{
			if(PropertyChanged==null)
				return;
			
			System.Windows.Threading.Dispatcher dispatcher=null;
			
			if(NeedChangeNotificationInvoke)
				dispatcher=System.Windows.Application.Current.Dispatcher;

			PropertyChangedEventArgs e=new PropertyChangedEventArgs(property_name);
			if((!NeedChangeNotificationInvoke)||(dispatcher.CheckAccess()))
			{
				PropertyChanged.Invoke(this,e);
			}
			else
			{
				Delegate[] invoke_list=PropertyChanged.GetInvocationList();
				foreach(Delegate handler in invoke_list)
				{
					dispatcher.BeginInvoke(handler,this,e);
				}
			}
		}
		
		public event NotifyCollectionChangedEventHandler CollectionChanged;
		
		protected void On_collection_reset()
		{
			if(CollectionChanged==null)
				return;
			
			NotifyCollectionChangedEventArgs e=
				new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
			On_collection_changed(e);
		}
		
		protected void On_collection_changed
			(NotifyCollectionChangedEventArgs e)
		{
			if(CollectionChanged==null)
				return;

			System.Windows.Threading.Dispatcher dispatcher=null;
			
			if(NeedChangeNotificationInvoke)
				dispatcher=System.Windows.Application.Current.Dispatcher;
			if((!NeedChangeNotificationInvoke)||(dispatcher.CheckAccess()))
			{
				CollectionChanged.Invoke
					(this,
					 e);
			}
			else
			{
				Delegate[] invoke_list=CollectionChanged.GetInvocationList();
				foreach(Delegate handler in invoke_list)
				{
					dispatcher.BeginInvoke(handler,this,e);
				}
			}
		}
		
		protected void On_collection_add_range
			(IEnumerable<T> added)
		{
			if(CollectionChanged==null)
				return;
			
			NotifyCollectionChangedEventArgs e=
				new NotifyCollectionChangedEventArgs
				(NotifyCollectionChangedAction.Add,
				 new List<T>(added));
			
			On_collection_changed(e);
		}
		
		#endregion
		
		#region sort implementation
		private class List_comparer:IComparer<long>
		{
			public List_comparer(ListSortDirection direction)
			{
				if(direction==ListSortDirection.Descending)
					sort_intern=-1;
			}
			
			private int sort_intern=1;
			public int Compare(long x,long y)
			{
				if(x>y)
					return sort_intern;
				if(y>x)
					return -sort_intern;
				return 0;
			}
		}
		
		private void init_list_intern(string key,ListSortDirection direction)
		{
			PropertyDescriptorCollection pdc=TypeDescriptor.GetProperties(typeof(T));
			PropertyDescriptor finded=pdc.Find(key,true);
			if(finded==null)
				throw new ArgumentException("Cannot find sort property.");
			if(finded.PropertyType!=typeof(long))
				throw new ArgumentException("Sort property must be long type.");
			key_property=finded;
			
			IComparer<long> comparer=new List_comparer(direction);
			list_intern=new SortedList<long, T>(comparer);
		}
		#endregion
		
		#region must implement
		protected abstract string create_store_file_name();
		
		#endregion
		
		#region specific
		
		public void ForceReset()
		{
			On_collection_reset();
		}
		
		public void Load()
		{
			load_from_file_internal();
		}
		
		public BackgroundWorker BeginLoad()
		{
			/*
			 * Для коллекции твитов это рабоотает
			 * криво, потому что кэширование не страбатывает
			 * как надо
			 */
			
			BackgroundWorker bw=new BackgroundWorker();
			
			bw.DoWork+=(s,e)=>load_from_file_internal();
			bw.WorkerReportsProgress=false;
			bw.WorkerSupportsCancellation=false;
			
			bw.RunWorkerAsync();
			
			return bw;
		}
		
		private void load_from_file_internal()
		{
			string file_name=create_store_file_name();
			if(System.IO.File.Exists(file_name))
			{
				Newtonsoft.Json.JsonSerializer serializer=new Newtonsoft.Json.JsonSerializer();
				System.IO.StreamReader reader=new System.IO.StreamReader(file_name,System.Text.Encoding.UTF8);
				List<T> readed=serializer.Deserialize(reader,typeof(List<T>)) as List<T>;
				foreach (var item in readed)
				{
					//чтобы нормально отработалось кэширование
					item.Client=Client;
					item.Credentials=Credentials;
				}
				reader.Close();
				if(readed!=null)
				{
					List<T> added_t;
					this.AddRange(readed,out added_t);
				}
			}
		}
		
		public BackgroundWorker BeginStore()
		{
			BackgroundWorker bw=new BackgroundWorker();
			
			bw.DoWork+=(s,e)=>store_internal();
			bw.WorkerReportsProgress=false;
			bw.WorkerSupportsCancellation=false;
			
			bw.RunWorkerAsync();
			
			return bw;
		}
		
		public void Store()
		{
			store_internal();
		}
		
		private void store_internal()
		{
			string file_name=create_store_file_name();
			
			Newtonsoft.Json.JsonSerializer serializer=new Newtonsoft.Json.JsonSerializer();
			System.IO.StreamWriter writer=new System.IO.StreamWriter(file_name,false,System.Text.Encoding.UTF8);
			List<T> list_to_serialize=new List<T>(this);
			serializer.Serialize(writer,list_to_serialize);
			writer.Close();
		}
		
		private bool m_UpdateInProgress=false;
		
		protected void On_begin_update()
		{
			lock(SyncRoot)
			{
				if(m_UpdateInProgress)
					return;
				
				m_UpdateInProgress=true;
			}
			
			On_property_changed("UpdateInProgress");
		}
		
		protected void On_end_update()
		{
			lock(SyncRoot)
			{
				if(!m_UpdateInProgress)
					return;
				
				m_UpdateInProgress=false;
			}
			
			On_property_changed("UpdateInProgress");
		}
		
		public bool UpdateInProgress
		{
			get
			{
				bool ret=false;
				lock(SyncRoot)
				{
					ret=m_UpdateInProgress;
				}
				return ret;
			}
		}
		
		public long MaxKey
		{
			get
			{
				long ret=-1;
				
				lock(SyncRoot)
				{
					if(list_intern.Count==0)
					{
						ret=-1;
					}
					else if(list_intern.Count==1)
					{
						ret=list_intern.Keys[0];
					}
					else
					{
						if(list_intern.Keys[0]<list_intern.Keys[1])
						{
							//accend
							ret=list_intern.Keys[list_intern.Count-1];
						}
						else
						{
							//desc
							ret=list_intern.Keys[0];
						}
					}
				}
				return ret;
			}
		}
		
		/// <summary>
		/// Использовать с осторожностью при перечислении
		/// чтоб во время перечисления
		/// не было бы записи в список
		/// </summary>
		public virtual object SyncRoot
		{
			get
			{
				return (list_intern as ICollection).SyncRoot;
			}
		}
		
		public virtual void AddRange(IEnumerable<T> range, out List<T> added_range)
		{
			List<T> items_really_added=new List<T>();
			
			foreach(T item in range)
			{
				int new_ind=-1;
				lock(SyncRoot)
				{
					new_ind=add_with_check(item);
				}
				if(new_ind>-1)
				{
					items_really_added.Add(item);
					
					On_collection_changed
						(new NotifyCollectionChangedEventArgs
						 (NotifyCollectionChangedAction.Add,
						  item,
						  new_ind));
					
//					#if DEBUG
//					Helper.OnDebugMessage(string.Format("Add item to timeline ind={0}",new_ind));
//					#endif
				}
			}
			added_range=items_really_added;
		}
		
		private int add_with_check(T item)
		{
			long key_value=(long)key_property.GetValue(item);
			if(list_intern.ContainsKey(key_value))
			{
				return -1;
			}
			else
			{
				list_intern.Add(key_value,item);
				return list_intern.IndexOfKey(key_value);
			}
		}
		
		public virtual T TryGet(long key)
		{
			T ret;
			lock(SyncRoot)
			{
				list_intern.TryGetValue(key,out ret);
			}
			return ret;
		}
		
		public virtual T this[long key]
		{
			get
			{
				T ret;
				lock(SyncRoot)
				{
					ret=list_intern[key];
				}
				return ret;
			}
			set
			{
				
				if((long)key_property.GetValue(value)!=key)
				{
					throw new ArgumentException("Key of the value must be equal to indexer");
				}
				
				T replaced_item=null;
				int replace_ind=-1;
				int new_ind=-1;
				lock(SyncRoot)
				{
					replace_ind=list_intern.IndexOfKey(key);
					if(replace_ind>-1)
					{
						replaced_item=list_intern.Values[replace_ind];
					}
					
					list_intern[key]=value;
					
					if(replace_ind==-1)
					{
						new_ind=list_intern.IndexOfKey(key);
					}
				}
				
				NotifyCollectionChangedEventArgs e;
				if(replace_ind>-1)
				{
					e=new NotifyCollectionChangedEventArgs
						(NotifyCollectionChangedAction.Replace,
						 value,
						 replaced_item,
						 replace_ind);
				}
				else
				{
					e=new NotifyCollectionChangedEventArgs
						(NotifyCollectionChangedAction.Add,
						 value,
						 new_ind);
				}
				On_collection_changed(e);
			}
		}
		
		#endregion
		
		#region IList implementation
		public virtual int Count
		{
			get
			{
				int ret=0;
				lock(SyncRoot)
				{
					ret=list_intern.Count;
				}
				return ret;
			}
		}
		
		public virtual bool IsReadOnly
		{
			get{return false;}
		}
		
		public virtual T this[int index]
		{
			get
			{
				T ret = default(T);
				lock(SyncRoot)
				{
					if (list_intern.Values.Count > 0)
					{
						ret = list_intern.Values[index];
					}
				}
				return ret;
			}
			set
			{
				throw new NotSupportedException();
			}
		}
		
		public virtual void Add(T item)
		{
			lock(SyncRoot)
			{
				list_intern.Add((long)key_property.GetValue(item),item);
			}
			
			On_collection_changed
				(new NotifyCollectionChangedEventArgs
				 (NotifyCollectionChangedAction.Add,
				  item));
		}
		
		public virtual void Clear()
		{
			lock(SyncRoot)
			{
				list_intern.Clear();
			}
			
			On_collection_reset();
		}
		
		public virtual bool Contains(T item)
		{
			bool ret;
			lock(SyncRoot)
			{
				ret=list_intern.ContainsValue(item);
			}
			return ret;
		}
		
		public virtual void CopyTo(T[] array,int arrayIndex)
		{
			lock(SyncRoot)
			{
				list_intern.Values.CopyTo(array,arrayIndex);
			}
		}

		public virtual IEnumerator GetEnumerator()
		{
			IEnumerator ret;
			lock(SyncRoot)
			{
				ret=list_intern.Values.GetEnumerator();
			}
			return ret;
		}
		
		IEnumerator<T> IEnumerable<T>.GetEnumerator()
		{
			IEnumerator<T> ret;
			lock(SyncRoot)
			{
				ret=list_intern.Values.GetEnumerator();
			}
			return ret;
		}
		
		public virtual int IndexOf(T item)
		{
			int ret;
			lock(SyncRoot)
			{
				ret=list_intern.IndexOfValue(item);
			}
			return ret;
		}
		
		public virtual void Insert(int index,T item)
		{
			throw new NotSupportedException();
		}
		
		public virtual bool Remove(T item)
		{
			bool ret;
			lock(SyncRoot)
			{
				ret=list_intern.Remove((long)key_property.GetValue(item));
			}
			
			if(ret)
			{
				On_collection_changed
					(new NotifyCollectionChangedEventArgs
					 (NotifyCollectionChangedAction.Remove,
					  item));
			}
			
			return ret;
		}

		public virtual void RemoveAt(int index)
		{
			T removed;
			lock(SyncRoot)
			{
				removed=list_intern.Values[index];
				
				list_intern.RemoveAt(index);
			}
			
			On_collection_changed
				(new NotifyCollectionChangedEventArgs
				 (NotifyCollectionChangedAction.Remove,
				  removed,
				  index));
		}
		#endregion
		
		#region IList non-generic implementation
		bool ICollection.IsSynchronized
		{
			get{return true;}
		}
		void ICollection.CopyTo(Array array,int index)
		{
			int cur_ind=index;
			foreach(var item in list_intern.Values)
			{
				if(cur_ind==list_intern.Values.Count)
					throw new IndexOutOfRangeException();
				array.SetValue(item,cur_ind);
				cur_ind++;
			}
		}
		bool IList.IsFixedSize
		{
			get{return false;}
		}
		object IList.this[int index]
		{
			get{return this[index];}
			set{this[index]=(T)value;}
		}
		void IList.Remove(object item)
		{
			Remove((T)item);
		}
		void IList.Insert(int index,object item)
		{
			Insert(index,(T)item);
		}
		int IList.IndexOf(object item)
		{
			return IndexOf((T)item);
		}
		bool IList.Contains(object item)
		{
			return Contains((T)item);
		}
		int IList.Add(object item)
		{
			Add((T)item);
			return IndexOf((T)item);
		}
		#endregion
	}
}
